package com.sunday.common.easy.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.sunday.common.core.enums.ErrorCodeEnum;
import com.sunday.common.core.exception.BusinessException;
import com.sunday.common.core.exception.CoreException;
import com.sunday.common.core.gson.GsonUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;

/**
 * 默认分段读取，部分提交，需要注意异常时的部分结果响应
 * https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read
 *
 * @author zsy
 * @since 2022/12/7
 */
@Slf4j
public class EasyExcelReadListener<T> implements ReadListener<T> {

    private static final int DEFAULT_READ_MAX_COUNT = 10000;

    private static final int DEFAULT_BATCH_COUNT = 1000;

    /**
     * 指定提交条数数量
     */
    private int batchCount;
    /**
     * 最大读取条数
     */
    private long readMaxCount;
    /**
     * 进行方法实现
     */
    private Consumer<List<T>> dataProcessor;
    /**
     * 解析异常是否阻断, 内部业务异常必须阻断
     */
    private boolean ignoreError;
    /**
     * 临时存储
     */
    private List<T> cachedDataList;
    /**
     * 执行计数器
     */
    private Counter counter;

    public EasyExcelReadListener(Consumer<List<T>> dataProcessor) {
        this(DEFAULT_BATCH_COUNT, dataProcessor, Counter.create());
    }

    public EasyExcelReadListener(Consumer<List<T>> dataProcessor, Counter counter) {
        this(DEFAULT_BATCH_COUNT, dataProcessor, counter);
    }

    public EasyExcelReadListener(int batchCount, Consumer<List<T>> dataProcessor) {
        this(batchCount, DEFAULT_READ_MAX_COUNT, dataProcessor, Counter.create());
    }

    public EasyExcelReadListener(int batchCount, Consumer<List<T>> dataProcessor, Counter counter) {
        this(batchCount, DEFAULT_READ_MAX_COUNT, dataProcessor, counter);
    }

    public EasyExcelReadListener(int batchCount, long readMaxCount, Consumer<List<T>> dataProcessor) {
        this(batchCount, readMaxCount, true, dataProcessor, Counter.create());
    }

    public EasyExcelReadListener(int batchCount, long readMaxCount, Consumer<List<T>> dataProcessor, Counter counter) {
        this(batchCount, readMaxCount, true, dataProcessor, counter);
    }

    public EasyExcelReadListener(int batchCount, long readMaxCount, boolean ignoreError, Consumer<List<T>> dataProcessor, Counter counter) {
        Assert.isTrue(batchCount <= readMaxCount, "BatchCount cannot be greater than ReadMaxCount");
        Assert.notNull(counter, "counter must not be null");
        this.batchCount = batchCount;
        this.readMaxCount = readMaxCount;
        this.ignoreError = ignoreError;
        this.dataProcessor = dataProcessor;
        this.counter = counter;
        this.cachedDataList = ListUtils.newArrayListWithExpectedSize(batchCount);
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
//        log.info("解析到一条数据:{}", data);
        cachedDataList.add(data);
        if (counter.getPosition().incrementAndGet() > readMaxCount) {
            throw new BusinessException(ErrorCodeEnum.A0_425);
        }
        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (cachedDataList.size() >= batchCount) {
            dataProcessor.accept(cachedDataList);
            counter.update(cachedDataList.get(cachedDataList.size() - 1));
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(batchCount);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (cachedDataList.size() > 0) {
            // 这里也要保存数据，确保最后遗留的数据也存储到数据库
            dataProcessor.accept(cachedDataList);
            counter.update(cachedDataList.get(cachedDataList.size() - 1));
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(batchCount);
        }
        log.info("所有数据解析完成！");
    }

    /**
     * 所有的监听器接收这个方法时，任何一个监听器做一个错误报告。如果在此抛出异常，则整个读取将终止
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        // log.error("解析失败，但是继续解析下一行 : {}", exception.getMessage());
        log.error("Excel 解析失败 : {}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException excelDataConvertException) {
            log.error("第 {} 行，第 {} 列解析异常，数据为 : {}",
                    excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(),
                    excelDataConvertException.getCellData());
            StringBuffer sb = new StringBuffer();
            sb.append("[第 ").append(excelDataConvertException.getRowIndex())
                    .append(" 行，第 ").append(excelDataConvertException.getColumnIndex())
                    .append(" 列解析异常]");
            throw new BusinessException(ErrorCodeEnum.A0_700, message(sb.toString()), true);
        }
        if (exception instanceof CoreException coreException) {
            throw new BusinessException(coreException.getCode(), coreException.getMessage(), message(null), true);
        }
        if (ignoreError) {
            throw new BusinessException(ErrorCodeEnum.A0_700, message(null), true);
        }
    }

    public String message(String msg) {
        StringBuffer sb = new StringBuffer();
        if (StringUtils.isNotBlank(msg)) {
            sb.append(msg).append(",");
        }
        sb.append("已完成")
                .append(counter.lastSubmitPosition)
                .append("条入库,")
                .append("最后数据 : ")
                .append(counter.lastSubmitInfo)
                .append(", 请核对后再次提交!");
        return sb.toString();
    }

    /**
     * 计数器
     *
     * @author zsy
     * @since 2022/12/7
     */
    @Data
    public static class Counter<T> {
        private Counter() {
        }

        /**
         * 当前指针
         */
        private AtomicLong position = new AtomicLong();
        /**
         * 最后提交定位
         */
        private long lastSubmitPosition;
        /**
         * 最后提交信息
         */
        private T lastSubmitInfo;

        /**
         * 更新最后节点信息
         * @param last
         */
        public void update(T last) {
            // 将最后提交指针位置更新
            lastSubmitPosition = position.get();
            // 将最后提交对象更新
            lastSubmitInfo = last;
        }

        public static Counter create() {
            return new Counter();
        }

        @Override
        public String toString() {
            return GsonUtils.DEFAULT.toJson(this);
        }

    }


}
